// $Id: multipart.c 81 2011-04-24 07:37:10Z cedric.shih@gmail.com $
/*
 * Copyright (C) 2011 Cedric Shih <cedric.shih@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#include <nio.h>
#include <nio/buffer.h>
#include <nio/handler.h>
#include <niohttp.h>

#include "log.h"
#include "headers.h"

#include "codec/multipart.h"

struct niohttp_multipart_decoder *niohttp_multipart_decoder_new(void)
{
	struct niohttp_multipart_decoder *codec;

	niohttp_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niohttp_error("failed to calloc");
		goto exception;
	}

	return codec;

exception:
	niohttp_multipart_decoder_free(codec);
	return NULL;
}

void niohttp_multipart_decoder_free(struct niohttp_multipart_decoder *codec)
{
	niohttp_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	free(codec);
}

int niohttp_multipart_decoder_setup(struct niohttp_multipart_decoder *decoder,
		const char *boundary)
{
	int rc;

	niohttp_debug("setting up");

	if (!(decoder->boundary = strdup(boundary))) {
		niohttp_error("failed to strdup");
		rc = NIO_ENOMEM;
		goto finally;
	}

	if (!(decoder->message.headers = niohttp_headers_new())) {
		niohttp_error("failed to create header");
		rc = NIO_ENOMEM;
		goto finally;
	}

	decoder->state = NIOHTTP_MULTIPART_HEADER_NAME;
	decoder->mark = 0;
	decoder->offset1 = decoder->offset2 = -1;
	rc = 0;

finally:
	return rc;
}

void niohttp_multipart_decoder_reset(struct niohttp_multipart_decoder *decoder)
{
	niohttp_debug("resetting");

	niohttp_headers_clear(decoder->message.headers);
	decoder->state = NIOHTTP_MULTIPART_HEADER_NAME;
	decoder->mark = 0;
	decoder->offset1 = decoder->offset2 = -1;
}

void niohttp_multipart_decoder_teardown(
		struct niohttp_multipart_decoder *decoder)
{
	if (decoder->boundary) {
		free(decoder->boundary);
		decoder->boundary = NULL;
	}

	if (decoder->message.headers) {
		niohttp_headers_free(decoder->message.headers);
		decoder->message.headers = NULL;
	}
}

int niohttp_multipart_decoder_parse(struct niohttp_multipart_decoder *decoder,
		struct niobuffer *buffer,
		struct niohttp_multipart_message **message)
{
	int rc, b;
	unsigned char *data = niobuffer_array(buffer);
	size_t pos;

	niohttp_debug("skipping parsed: %d", decoder->mark);

	if ((rc = niobuffer_skip(buffer, decoder->mark))) {
		niohttp_error("failed to skip buffer: %s",
				nio_strerror(rc));
		goto finally;
	}

	while ((decoder->state != NIOHTTP_MULTIPART_CONTENT) &&
			niobuffer_remaining(buffer) > 0) {
		switch (decoder->state) {
		case NIOHTTP_MULTIPART_HEADER_NAME:
			pos = niobuffer_position(buffer);
			if (decoder->offset1 == -1 && data[pos] == '\r') {
				niohttp_debug("hit end of headers: %d", pos);

				if ((rc = niobuffer_skip(buffer, 1))) {
					niohttp_error("failed to skip: %s",
							nio_strerror(rc));
					goto finally;
				}

				decoder->state = NIOHTTP_MULTIPART_HEADER;
				break;
			}
		case NIOHTTP_MULTIPART_HEADER_VALUE:
		case NIOHTTP_MULTIPART_HEADER_ENTRY:
			if ((rc = niohttp_multipart_decoder_parse_header(
					decoder, buffer))) {
				niohttp_error("failed to parse header");
				goto finally;
			}
			break;
		case NIOHTTP_MULTIPART_HEADER:
			if ((rc = niobuffer_get(buffer, &b))) {
				niohttp_error("failed to get buffer: %s",
						nio_strerror(rc));
				goto finally;
			}

			if ((b < ' ' || b > '~') && (b != '\r' && b != '\n')) {
				niohttp_error("invalid encoding");
				rc = -1;
				goto finally;
			}

			if (b != '\n') {
				niohttp_error("unexpected end char: %d", b);
				rc = -1;
				goto finally;
			}

			niohttp_debug("hit end of meta data: %d",
					niobuffer_position(buffer) - 1);
			decoder->state = NIOHTTP_MULTIPART_HEADERS;
			break;
		case NIOHTTP_MULTIPART_HEADERS:
			// TODO: hit boundary string
			break;
		default:
			niohttp_error("unexpected state: %d", decoder->state);
			rc = -1;
			goto finally;
		}
	}

	niohttp_debug("marking parsed: %d", niobuffer_position(buffer));

	decoder->mark = niobuffer_position(buffer);

	if (decoder->state == NIOHTTP_MULTIPART_CONTENT) {
		*message = &decoder->message;
	} else {
		*message = NULL;
	}

	rc = 0;

finally:
	return rc;
}

int niohttp_multipart_decoder_parse_header(
		struct niohttp_multipart_decoder *decoder,
		struct niobuffer *buffer)
{
	int rc, b;
	char *data = (char *)niobuffer_array(buffer);
	size_t offset;

	niohttp_debug("parsing header entry");

	while (niobuffer_remaining(buffer) > 0) {
		if (decoder->offset1 == -1) {
			niohttp_debug("marking header name at: %d",
					niobuffer_position(buffer));

			decoder->offset1 = niobuffer_position(buffer);
		}

		if ((rc = niobuffer_get(buffer, &b))) {
			niohttp_error("failed to get buffer: %s",
					nio_strerror(rc));
			goto finally;
		}

		if ((b < ' ' || b > '~') && (b != '\r' && b != '\n')) {
			niohttp_error("invalid encoding");
			rc = -1;
			goto finally;
		}

		niohttp_trace("processing: %d", b);

		if (decoder->state == NIOHTTP_MULTIPART_HEADER_NAME) {
			if (b == ':') {
				offset = niobuffer_position(buffer) - 1;

				niohttp_debug("hit end of header name: %d",
						offset);

				decoder->state = NIOHTTP_MULTIPART_HEADER_VALUE;
			}
		} else if (decoder->state == NIOHTTP_MULTIPART_HEADER_VALUE) {
			if (decoder->offset2 == -1) {
				if (b != ' ') {
					niohttp_error("malformed header");
					rc = -1;
					goto finally;
				}

				offset = niobuffer_position(buffer);

				niohttp_debug("marking header value at: %d",
						offset);

				decoder->offset2 = offset;
			}

			if (b == '\r') {
				offset = niobuffer_position(buffer) - 1;

				niohttp_debug("hit end of header value: %d",
						offset);

				if ((rc = niohttp_headers_nset(
						decoder->message.headers,
						data + decoder->offset1,
						decoder->offset2 - 2 -
						decoder->offset1,
						data + decoder->offset2,
						offset - decoder->offset2))) {
					niohttp_error("failed to set header: "
							"%s",
							niohttp_strerror(rc));
					goto finally;
				}

				decoder->state = NIOHTTP_MULTIPART_HEADER_ENTRY;
			}
		} else if (decoder->state == NIOHTTP_MULTIPART_HEADER_ENTRY) {
			if (b != '\n') {
				niohttp_error("unexpected end char: %d", b);
				rc = -1;
				goto finally;
			}

			offset = niobuffer_position(buffer) - 1;

			niohttp_debug("hit end of header entry: %d", offset);

			decoder->state = NIOHTTP_MULTIPART_HEADER_NAME;
			decoder->offset1 = decoder->offset2 = -1;
			break;
		}

		niohttp_trace("continuing");
	}

	rc = 0;

finally:
	return rc;
}

struct niohttp_multipart_encoder *niohttp_multipart_encoder_new(void)
{
	struct niohttp_multipart_encoder *codec;

	niohttp_debug("constructing");

	if (!(codec = calloc(1, sizeof(*codec)))) {
		niohttp_error("failed to calloc");
		goto exception;
	}

	return codec;

exception:
	niohttp_multipart_encoder_free(codec);
	return NULL;
}

void niohttp_multipart_encoder_free(struct niohttp_multipart_encoder *codec)
{
	niohttp_debug("destructing: %p", codec);

	if (!(codec)) {
		return;
	}

	free(codec);
}

int niohttp_multipart_encoder_setup(struct niohttp_multipart_encoder *encoder,
		struct niohttp_headers *headers)
{
	niohttp_debug("setting up");
	encoder->headers = headers;
	return 0;
}

void niohttp_multipart_encoder_teardown(
		struct niohttp_multipart_encoder *encoder)
{
	niohttp_debug("tearing down");
	encoder->headers = NULL;
}

int niohttp_multipart_encoder_format(struct niohttp_multipart_encoder *encoder,
		struct niobuffer *buffer)
{
	int rc;
	struct niohttp_header_list *list;
	struct niohttp_header *header;

	niohttp_debug("formatting multipart message to buffer");

	if (encoder->headers) {
		niohttp_debug("putting headers");

		list = niohttp_headers_list(encoder->headers);

		TAILQ_FOREACH(header, list, next) {
			if ((rc = niobuffer_putf(buffer, "%s: %s\r\n",
					header->name, header->value))) {
				niohttp_error("failed to put header: %s",
						nio_strerror(rc));
				rc = NIOHTTP_EBUFFER;
				goto finally;
			}
		}
	}

	niohttp_debug("putting end of header");

	if ((rc = niobuffer_putf(buffer, "%s", "\r\n"))) {
		niohttp_error("failed to put end of header: %s",
				nio_strerror(rc));
		rc = NIOHTTP_EBUFFER;
		goto finally;
	}

	rc = 0;

finally:
	return rc;
}
